/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

import EventEmitter from 'events';

import { parseJsonFile } from '../../../../common/util/parse-json/parse-json-util.js';
import { CoreTask } from '../../../external/task/core-task.js';
import { hvigorConfigFileTsRoot, hvigorfileTSRoots, targetModules, targetRoots } from '../../util/ts_check.js';
import { getModuleFromTaskPath } from '../util/task-module-util.js';
import { parseTaskPath } from '../util/task-path-util.js';

import { projectTaskDag } from './task-directed-acyclic-graph.js';
import { TaskQueue } from './task-queue.js';

/**
 * 用于管理任务执行状态
 *
 * @since 2022/08/13
 */
export class TaskControlCenter extends EventEmitter {
  private readonly blockedTaskMap: Map<string, number>;
  private readonly runnableTaskQueue: TaskQueue;
  private readonly runningTaskList: Map<string, CoreTask>;
  private intervalId: NodeJS.Timeout | undefined;

  constructor() {
    super();
    this.blockedTaskMap = new Map<string, number>();
    this.runnableTaskQueue = new TaskQueue();
    this.runningTaskList = new Map<string, CoreTask>();
  }

  clear() {
    this.clearRunnableTaskQueue();
    this.clearBlockedTaskMap();
    this.clearInterval();
    this.runningTaskList.clear();
  }

  initTaskQueueBeforeRun(executeTaskPaths: string[]): void {
    executeTaskPaths.forEach((taskPath) => this.initTaskQueueForOneTaskNew(taskPath));
  }

  private initTaskQueueForOneTaskNew(task: string): void {
    this.updateTargetRoots(task);
    const childrenTask: string[] = [...projectTaskDag.getChildren(task).values()];
    this.blockedTaskMap.set(task, childrenTask.length);
    if (childrenTask.length === 0) {
      this.runnableTaskQueue.push(task);
    } else {
      childrenTask.forEach((childTask) => {
        if (!this.blockedTaskMap.has(childTask)) {
          this.initTaskQueueForOneTaskNew(childTask);
        }
      });
    }
  }

  /**
   * 更新需要进行类型检查的构建脚本集合
   *
   * @param task 需要执行的任务
   */
  private updateTargetRoots(task: string): void {
    const { moduleName } = parseTaskPath(task);
    if (targetModules.has(moduleName)) {
      return;
    }
    const taskNode = getModuleFromTaskPath(task);
    if (!taskNode) {
      throw new Error(`Can not find node ${moduleName} with task ${task}`);
    }
    if (hvigorConfigFileTsRoot) {
      targetRoots.add(hvigorConfigFileTsRoot);
    }
    const buildScriptPath = `${taskNode.getBuildFilePath()}.ts`;
    if (!hvigorfileTSRoots.has(buildScriptPath)) {
      return;
    }
    targetRoots.add(buildScriptPath);
    targetModules.add(moduleName);
    const relatedModules: string[] | undefined = parseJsonFile(taskNode.getBuildProfilePath()).entryModules;
    if (!relatedModules) {
      return;
    }
    const project = taskNode.getProject();
    for (const module of relatedModules) {
      const relateModule = project.findModuleByName(module);
      if (!relateModule) {
        throw new Error(`Invalid related node ${module} from node ${moduleName} `);
      }
      const depBuildScriptPath = `${relateModule.getBuildFilePath()}.ts`;
      if (hvigorfileTSRoots.has(depBuildScriptPath)) {
        targetRoots.add(depBuildScriptPath);
        targetModules.add(module);
      }
    }
  }

  public clearRunnableTaskQueue(): void {
    this.runnableTaskQueue.clear();
  }

  public clearBlockedTaskMap(): void {
    this.blockedTaskMap.clear();
  }

  public allTasksHasExecuted(): boolean {
    return this.blockedTaskMap.size === 0;
  }

  public popRunnableTask(): string | undefined {
    return this.runnableTaskQueue.pop();
  }

  public getRunnableTaskSize(): number {
    return this.runnableTaskQueue.size();
  }

  public setTaskExecuted(executedTaskPath: string): void {
    this.blockedTaskMap.delete(executedTaskPath);

    const tasksDependOnExecutedTask: Set<string> = projectTaskDag.getParent(executedTaskPath);

    tasksDependOnExecutedTask.forEach((blockedParentTaskPath) => {
      if (!this.blockedTaskMap.has(blockedParentTaskPath)) {
        return;
      }
      const blockedParentTaskOutEdge = this.blockedTaskMap.get(blockedParentTaskPath)! - 1;
      this.blockedTaskMap.set(blockedParentTaskPath, blockedParentTaskOutEdge);
      if (blockedParentTaskOutEdge === 0) {
        this.runnableTaskQueue.push(blockedParentTaskPath);
      }
    });
  }

  public pushRunningTask(task: CoreTask): void {
    this.runningTaskList.set(task.getPath(), task);
  }

  public getRunningTask(taskPath: string): CoreTask | undefined {
    return this.runningTaskList.get(taskPath);
  }

  public setIntervalId(intervalId: NodeJS.Timeout | undefined) {
    this.intervalId = intervalId;
  }

  public clearInterval() {
    clearInterval(this.intervalId as unknown as number);
  }

  public remove(task: string): string | undefined {
    return this.runnableTaskQueue.remove(task);
  }
}

export const taskControlCenter = new TaskControlCenter();
